/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sg_client.cpp - client management
//


#include "sg_local.h"


/*
 ==============================================================================

 COMMAND EXECUTION

 ==============================================================================
*/


/*
 ==================
 etClient::CheatsOK
 ==================
*/
bool etClient::CheatsOK () const {

	if (!glqCVarSystem->AllowCheats()){
		glqServer->AddReliableCommand(number, "print %s", "Cheats are not enabled on this server");
		return false;
	}

	if (com_developer->integerValue)
		return true;

	if (!entity || entity->health <= 0){
		glqServer->AddReliableCommand(number, "print %s", "You must be alive to use this command");
		return false;
	}

	return true;
}

/*
 ==================
 etClient::CmdGod
 ==================
*/
void etClient::CmdGod (){

	const char	*message;

	if (!CheatsOK())
		return;

	godMode = !godMode;

	if (godMode)
		message = "godMode ON";
	else
		message = "godMode OFF";

	glqServer->AddReliableCommand(number, "print %s", message);
}

/*
 ==================
 etClient::CmdNoClip
 ==================
*/
void etClient::CmdNoClip (){

	const char	*message;

	if (!CheatsOK())
		return;

	noClip = !noClip;

	if (noClip)
		message = "noClip ON";
	else
		message = "noClip OFF";

	glqServer->AddReliableCommand(number, "print %s", message);
}

/*
 ==================
 etClient::CmdNoTarget
 ==================
*/
void etClient::CmdNoTarget (){

	const char	*message;

	if (!CheatsOK())
		return;

	noTarget = !noTarget;

	if (noTarget)
		message = "noTarget ON";
	else
		message = "noTarget OFF";

	glqServer->AddReliableCommand(number, "print %s", message);
}

/*
 ==================
 
 ==================
*/
void etClient::CmdGive (){

	if (!CheatsOK())
		return;

	if (spectator || entity->health <= 0)
		return;

	// TODO: check for item strings, then add the desired item to the inventory
}

/*
 ==================
 etClient::CmdKill
 ==================
*/
void etClient::CmdKill (){

	if (spectator || entity->health <= 0)
		return;

	entity->Damage(entity, entity, vec3_origin, vec3_origin, DAMAGE_KILL, DAMAGE_NO_PROTECTION, MOD_SUICIDE);
}

/*
 ==================
 etClient::CmdSay
 ==================
*/
void etClient::CmdSay () const {

	etClient	*other;
	char		string[MAX_STRING_LENGTH];
	int			i;

	Str_SPrintf(string, sizeof(string), "%s" S_COLOR_DEFAULT ": %s", playerName, glqCmdSystem->Args(1, -1, false));

	// Print to system console
	if (com_dedicated->integerValue)
		G_Printf("%s\n", string);

	// Broadcast to all active clients
	for (i = 0, other = level.clients; i < MAX_CLIENTS; i++, other++){
		if (other->connected != CON_CONNECTED)
			continue;	// Not fully in game yet

		glqServer->AddReliableCommand(other->number, "chat %s", string);
	}
}

/*
 ==================
 
 ==================
*/
void etClient::CmdSayTeam () const {

	// TODO: this should be like a normal "say" command, but should only print to
	// team members (use a flag?)
}

/*
 ==================
 etClient::CmdGetViewPos
 ==================
*/
void etClient::CmdGetViewPos () const {

	glqServer->AddReliableCommand(number, "print %s : %s", GetViewOrigin().ToString(0), GetViewAngles().ToString(0));
}

/*
 ==================
 etClient::CmdSetViewPos
 ==================
*/
void etClient::CmdSetViewPos (){

	glqVec3		origin;
	glqAngles	angles;

	if (glqCmdSystem->Argc() != 4 && glqCmdSystem->Argc() != 5){
		G_Printf("Usage: setViewPos <x> <y> <z> [yaw]\n");
		return;
	}

	if (!CheatsOK())
		return;

	origin.Set(Str_ToFloat(glqCmdSystem->Argv(1)), Str_ToFloat(glqCmdSystem->Argv(2)), Str_ToFloat(glqCmdSystem->Argv(3)));

	if (glqCmdSystem->Argc() == 4)
		angles.Clear();
	else
		angles.Set(0.0f, Str_ToFloat(glqCmdSystem->Argv(4)), 0.0f);

	// Teleport the player
	Teleport(origin, angles);
}

/*
 ==================
 etClient::Command
 ==================
*/
void etClient::Command (){

	const char	*name;

	if (connected != CON_CONNECTED)
		return;		// Not fully in game yet

	name = glqCmdSystem->Argv(0);

	if (!Str_ICompare(name, "god"))
		CmdGod();
	else if (!Str_ICompare(name, "noClip"))
		CmdNoClip();
	else if (!Str_ICompare(name, "noTarget"))
		CmdNoTarget();
	else if (!Str_ICompare(name, "give"))
		CmdGive();
	else if (!Str_ICompare(name, "kill"))
		CmdKill();
	else if (!Str_ICompare(name, "say"))
		CmdSay();
	else if (!Str_ICompare(name, "sayTeam"))
		CmdSayTeam();
	else if (!Str_ICompare(name, "getViewPos"))
		CmdGetViewPos();
	else if (!Str_ICompare(name, "setViewPos"))
		CmdSetViewPos();
	else
		glqServer->AddReliableCommand(number, "print Unknown command \"%s\"", name);
}


/*
 ==============================================================================

 CLIENT SETUP

 ==============================================================================
*/


/*
 ==================
 etClient::UserInfoChanged

 Called from ClientConnect when the player first connects and directly by the
 server system when the player updates a user info variable.

 The game can override any of the settings if desired.
 ==================
*/
void etClient::UserInfoChanged (char *userInfo){

	char	string[MAX_STRING_LENGTH];
	int		i;

	// Parse cl_playerName
	if (!Info_GetValueForKey(userInfo, "cl_playerName", string))
		Str_Copy(string, "Unnamed Player", sizeof(string));
	else {
		// Make sure the player name is valid
		if (!Str_LengthWithoutColors(string))
			Str_Copy(string, "Unnamed Player", sizeof(string));
		else {
			for (i = 0; string[i]; i++){
				if (string[i] == '"'){
					Str_Copy(string, "Unnamed Player", sizeof(string));
					break;
				}

				if (!Str_CharIsPrintable(string[i])){
					Str_Copy(string, "Unnamed Player", sizeof(string));
					break;
				}
			}
		}
	}

	// Check if it has been changed
	if (connected == CON_CONNECTED){
		if (Str_Compare(string, playerName))
			glqServer->BroadcastReliableCommand("print %s" S_COLOR_DEFAULT " renamed to %s", playerName, string);
	}

	// Set the new player name
	Str_Copy(playerName, string, sizeof(playerName));

	Info_SetValueForKey(userInfo, "cl_playerName", playerName);

	// Parse cl_voiceChat
	if (!Info_GetValueForKey(userInfo, "cl_voiceChat", string))
		voiceChat = 3;
	else
		voiceChat = glqMath::ClampInt(Str_ToInteger(string), 0, 3);

	// Set the new voice chat setting
	Info_SetValueForKey(userInfo, "cl_voiceChat", Str_FromInteger(voiceChat));

	// Update config string with a subset of the user info so other clients can
	// print scoreboards, display models, and play sounds
	Info_Clear(string);
	Info_SetValueForKey(string, "cl_playerName", playerName);

	SG_SetConfigString(CS_CLIENTS + number, string);
}

/*
 ==================
 
 Called when a player begins connecting to the server and for every map change.

 The session information will be valid after exit.

 Returns NULL if the client is allowed to connect, otherwise returns a string
 with the reason for denial.

 Otherwise, the client will be sent the current game state and will eventually
 get to Begin.

 The firstTime parameter will be true the very first time a client connects to
 the server, but false on map changes and restarts.
 ==================
*/
const char *etClient::Connect (int clientNum, bool localClient, const char *ip, char *userInfo, bool firstTime){

	// IP filtering
	if (!localClient){
		if (SG_FilterIP(ip))
			return "You are banned from this server";
	}

	// Set up the client
	number = clientNum;
	isLocal = localClient;

	connected = CON_CONNECTING;

	if (localClient)
		level.localClient = this;

	// Set up physics
	physics.state = &state;

	physics.debug = false;
	physics.gravity = DEFAULT_GRAVITY;		// FIXME: would this mess up the gravity in e1m8?
	physics.userCmd = lastUserCmd;

	// TODO: add more physics stuff i needed

	// Get and distribute relevant parameters
	UserInfoChanged(userInfo);

	// Don't do the "xxx connected" messages if they were carried over from
	// the previous level
	if (firstTime)
		glqServer->BroadcastReliableCommand("print %s" S_COLOR_DEFAULT " connected", playerName);

	return NULL;
}

/*
 ==================
 etClient::Disconnect

 Called when a player drops from the server.
 Will not be called between levels.

 This should NOT be called directly by any game logic. Instead, call
 glqServer->DropClient, which will call this function and do server system
 housekeeping.
 ==================
*/
void etClient::Disconnect (){

	int		i;

	if (connected == CON_DISCONNECTED)
		return;

	if (isLocal)
		level.localClient = NULL;

	// Clear the entity
	if (entity)
		entity->Clear();

	// Free voice data
	for (i = 0; i < numVoiceMessages; i++)
		glqMemory->Free(voiceMessages[i].data);

	// Clear the client
	glqMemory->Fill(this, 0, sizeof(etClient));
}

/*
 ==================
 etClient::Begin

 Called when a player has finished connecting, and is ready to be placed into
 the level.
 
 This will happen every level load, and on transition between teams, but
 doesn't happen on respawns.
 ==================
*/
void etClient::Begin (){

	int		i;

	// If it was just connecting
	if (connected == CON_CONNECTING){
		connected = CON_CONNECTED;

		// Set up the entity
		entity = &level.entities[number];

		entity->Setup(number, ET_PLAYER, SG_ValidateEntityName(NULL, "player", number), "player", NULL);

		// Finish setting up
		entity->flags |= (FL_VISIBLE | FL_AUDIBLE | FL_PHYSICS | FL_PUSHER | FL_DAMAGEABLE);
	}

	// Free voice data
	for (i = 0; i < numVoiceMessages; i++)
		glqMemory->Free(voiceMessages[i].data);

	numVoiceMessages = 0;

	// Spawn the player in the world
	Spawn();
}

/*
 ==================
 
 Called every time a player is placed fresh in the world: after the first
 Begin, and after each respawn.

 Initializes all non-persistent parts of the player state.
 ==================
*/
void etClient::Spawn (){

	spawnPoint_t	*spawnPoint;

	// Find a spawn point
	if (!level.numSpawnPoints)
		G_Error("No spawn points");

	spawnPoint = &level.spawnPoints[game.random.RandInt() % level.numSpawnPoints];

	// Set up the player state
	if (noClip)
		state.pmType = PM_NOCLIP;
	else if (spectator)
		state.pmType = PM_SPECTATOR;
	else
		state.pmType = PM_NORMAL;

	state.pmFlags = 0;
	state.pmTime = 0;

	state.clientNum = number;

	state.origin = spawnPoint->origin;
	state.velocity.Clear();

	// TODO: set state.bounds

	if (spectator)
		state.viewHeight = pm_spectateBoxSize->floatValue * 0.5f;
	else
		state.viewHeight = pm_normalViewHeight->floatValue;

	SetViewAngles(spawnPoint->angles);

	state.weaponType = WP_SHOTGUN;

	SetViewAngles(spawnPoint->angles);

	state.groundEntityNum = ENTITYNUM_NONE;

	// TODO: initialize inventory

	// Restore health
	entity->health = 100;

	// Allow triggers to hurt this player
	entity->nextTriggerTime = 0;

	// Toggle the teleport flag so the client knows to not interpolate
	state.flags ^= EF_TELEPORT;

	entity->flags ^= FL_TELEPORT;

	// Update entity state
	G_PlayerStateToEntityState(&state, &entity->state);

	// Update position and orientation
	entity->currentOrigin = entity->GetOrigin();
	entity->currentAngles = entity->GetAngles();
	entity->currentAxis = entity->GetAngles().ToMat3();

	// Set the ground entity
	entity->groundEntity = NULL;

	// Set the clip mask

	// Set the clip model

	// Add to the clip model contents

	// Link the entity
	SG_LinkEntity(entity);

	// Hide or show the entity as needed
	if (spectator)
		entity->Hide();
	else
		entity->Show();

	// Kill anything at the destination
	if (!spectator && !noClip)
		SG_KillBox(entity);
}

/*
 ==================

 Called when the client is damaged
 ==================
*/
void etClient::Pain (etEntity *inflictor, etEntity *attacker, const glqVec3 &point, const glqVec3 &direction, int damage){

}

/*
 ==================

 Called when the client is killed
 ==================
*/
void etClient::Kill (etEntity *inflictor, etEntity *attacker, const glqVec3 &point, const glqVec3 &direction, int damage, meansOfDeath_t meansOfDeath){

}


/*
 ==============================================================================

 THINKING

 ==============================================================================
*/


/*
 ==================
 
 This will be called once for each client frame, which will usually be a couple
 times for each server frame on fast clients
 ==================
*/
void etClient::Think (const userCmd_t *userCmd){

	int		msec;

	if (connected != CON_CONNECTED)
		return;		// Not fully in game yet

	// Save the previous userCmd
	oldCmdFlags = lastUserCmd.flags;
	oldCmdButtons = lastUserCmd.buttons;

	lastUserCmd = *userCmd;

	// Sanity check the user command time to prevent speedup cheating
	if (lastUserCmd.time > level.time + 200)
		lastUserCmd.time = level.time + 200;
	if (lastUserCmd.time < level.time - 1000)
		lastUserCmd.time = level.time - 1000;

	msec = lastUserCmd.time - state.userCmdTime;
	if (msec < 1)
		return;

	if (msec > 200)
		msec = 200;

	// Set up the player state
	if (noClip)
		state.pmType = PM_NOCLIP;
	else if (spectator)
		state.pmType = PM_SPECTATOR;
	else if (entity->health <= 0)
		state.pmType = PM_DEAD;
	else
		state.pmType = PM_NORMAL;

	// Save position and orientation

	// Prepare for a move
	physics.debug = sg_debugMove->integerValue;

	physics.gravity = sg_gravity->floatValue;

	physics.userCmd = lastUserCmd;

	// Move the player
	physics.MovePlayer();

	// Update entity state
	G_PlayerStateToEntityState(&state, &entity->state);

	// Update position and orientation
	entity->currentOrigin = entity->GetOrigin();
	entity->currentAngles = entity->GetAngles();
	entity->currentAxis = entity->GetAngles().ToMat3();

	// Update the bounds

	// Update the ground entity
	if (state.groundEntityNum == ENTITYNUM_NONE)
		entity->groundEntity = NULL;
	else
		entity->groundEntity = &level.entities[state.groundEntityNum];

	// Link the entity
	SG_LinkEntity(entity);

	// Set touchable entities
	if (spectator){

		// TODO: a spectator should be able to touch teleport/door triggers

	}
	else {

	}

	// Check for respawning
	if (entity->health <= 0){
		if (!(oldCmdButtons & BUTTON_ATTACK) && (lastUserCmd.buttons & BUTTON_ATTACK))
			Spawn();
	}

	// Run weapon if needed
	if (entity->health > 0)
		RunWeapon();
}


/*
 ==============================================================================

 WEAPON INTERACTION

 ==============================================================================
*/


/*
 ==================

 NOTE: this will be a generic function which handles ALL weapon animations, we will set the frames in each of
 the weapon functions, then they will be used here so that the function knows how many frames to advance.

 NOTE: Quake used different GAME_FRAMEMSEC so the animation is fast as hell, find out how to do it in regular speed
 ==================
*/
void etClient::UpdateWeaponState (){

}

/*
 ==================

 ==================
*/
void etClient::AxeWeapon (){

	// TODO: we should just add melee damage here
}

/*
 ==================
 etClient::ShotgunWeapon

 NOTE: this has 0-6 frames
 ==================
*/
void etClient::ShotgunWeapon (){

	SG_FireProjectile(PT_SHELL, entity, 15);
}

/*
 ==================
 etClient::SuperShotgunWeapon

 NOTE: this has 0-6 frames
 ==================
*/
void etClient::SuperShotgunWeapon (){

	SG_FireProjectile(PT_SHELL, entity, 15);
}

/*
 ==================
 etClient::NailgunWeapon

 NOTE: this has 0-8 frames
 ==================
*/
void etClient::NailgunWeapon (){

	SG_FireProjectile(PT_SPIKE, entity, 15);
}

/*
 ==================
 etClient::SuperNailgunWeapon

 NOTE: this has 0-8 frames
 ==================
*/
void etClient::SuperNailgunWeapon (){

	SG_FireProjectile(PT_SPIKE, entity, 15);
}

/*
 ==================
 etClient::GrenadeLauncherWeapon

 NOTE: this has 0-6 frames
 ==================
*/
void etClient::GrenadeLauncherWeapon (){

	SG_FireProjectile(PT_GRENADE, entity, 15);
}

/*
 ==================
 etClient::RocketLauncherWeapon

 NOTE: this has 0-6 frames
 ==================
*/
void etClient::RocketLauncherWeapon (){

	startFrame = 0;
	endFrame = 7;
	frameTime = level.time + 180 + GAME_FRAMEMSEC;

	SG_FireProjectile(PT_ROCKET, entity, 15);
}

/*
 ==================
 etClient::LightningGunWeapon

  NOTE: this has 0-4 frames
 ==================
*/
void etClient::LightningGunWeapon (){

	SG_FireProjectile(PT_LIGHTNING, entity, 15);
}

/*
 ==================

 ==================
*/
void etClient::SelectWeapon (){

	// TODO: select the weapon based on inventory status
}

/*
 ==================

 ==================
*/
void etClient::DropWeapon (){

	// TODO: here we spawn an entity and throw it out, USE BULLET PHYSICS!
}

/*
 ==================
 etClient::FireWeapon
 ==================
*/
void etClient::FireWeapon (){

	// Fire the appropriate projectile
	switch (state.weaponType){
	case WP_NONE:

		break;
	case WP_AXE:
		AxeWeapon();
		break;
	case WP_SHOTGUN:
		ShotgunWeapon();
		break;
	case WP_SUPERSHOTGUN:
		SuperShotgunWeapon();
		break;
	case WP_NAILGUN:
		SuperNailgunWeapon();
		break;
	case WP_SUPERNAILGUN:
		SuperNailgunWeapon();
		break;
	case WP_GRENADELAUNCHER:
		GrenadeLauncherWeapon();
		break;
	case WP_ROCKETLAUNCHER:
		RocketLauncherWeapon();
		break;
	case WP_LIGHTNINGUN:
		LightningGunWeapon();
		break;
	default:
		G_Error("etClient::FireWeapon: bad weapon type");
	}

	// Add an event
	AddEvent(EV_FIRE_WEAPON, 0);
}

/*
 ==================

 Runs weapons and updates the current weapon state
 ==================
*/
void etClient::RunWeapon (){

	if (entity->health <= 0 || spectator)
		return;

	if (!(oldCmdButtons & BUTTON_ATTACK) && (lastUserCmd.buttons & BUTTON_ATTACK))
		FireWeapon();

	// FIXME: we need to update the weapon animation here, do this similar to cg_testModelAnimation?
	
	UpdateWeaponState();
}


// ============================================================================


/*
 ==================
 etClient::Teleport

 This will skip interpolation for the client-game prediction, which looks
 like a teleportation
 ==================
*/
void etClient::Teleport (const glqVec3 &origin, const glqAngles &angles, bool keepMomentum){

	state.origin = origin;

	SetViewAngles(angles);

	state.groundEntityNum = ENTITYNUM_NONE;

	// Clear the velocity if desired
	if (!keepMomentum)
		state.velocity.Clear();

	// Toggle the teleport flag so the client knows to not interpolate
	state.flags ^= EF_TELEPORT;

	entity->flags ^= FL_TELEPORT;

	// Update entity state
	G_PlayerStateToEntityState(&state, &entity->state);

	// Update position and orientation
	entity->currentOrigin = entity->GetOrigin();
	entity->currentAngles = entity->GetAngles();
	entity->currentAxis = entity->GetAngles().ToMat3();

	// Clear the ground entity
	entity->groundEntity = NULL;

	// Link the entity
	SG_LinkEntity(entity);

	// Kill anything at the destination
	if (!spectator && !noClip)
		SG_KillBox(entity);
}


// ============================================================================


/*
 ==================
 etClient::SetViewAngles
 ==================
*/
void etClient::SetViewAngles (const glqAngles &angles){

	state.viewAngles = angles;

	state.deltaAngles[0] = angles[0] - SHORT2ANGLE(lastUserCmd.angles[0]);
	state.deltaAngles[1] = angles[1] - SHORT2ANGLE(lastUserCmd.angles[1]);
	state.deltaAngles[2] = angles[2] - SHORT2ANGLE(lastUserCmd.angles[2]);
}

/*
 ==================
 etClient::GetViewOrigin
 ==================
*/
glqVec3 etClient::GetViewOrigin () const {

	glqVec3		origin;
	glqAngles	angles;

	origin = state.origin;
	origin.z += state.viewHeight;

	return origin;
}

/*
 ==================
 etClient::GetViewAngles
 ==================
*/
glqAngles etClient::GetViewAngles () const {

	glqAngles	angles;

	angles = state.viewAngles;

	return angles;
}

/*
 ==================
 etClient::GetViewAxis
 ==================
*/
glqMat3 etClient::GetViewAxis () const {

	glqAngles	angles;

	angles = state.viewAngles;

	return angles.ToMat3();
}


// ============================================================================


/*
 ==================
 etClient::WriteGameState
 ==================
*/
void etClient::WriteGameState (msg_t *msg, int offset){

	if (connected != CON_CONNECTING)
		return;		// Fully in game already

	SG_WriteGameState(msg, offset);
}

/*
 ==================
 etClient::WriteSnapshot
 ==================
*/
void etClient::WriteSnapshot (msg_t *msg, int sequence, int deltaMessage, bool rateDelayed){

	if (connected != CON_CONNECTED)
		return;		// Not fully in game yet

	SG_WriteSnapshotToClient(this, msg, sequence, deltaMessage, rateDelayed);
}


// ============================================================================


/*
 ==================
 etClient::SetPing
 ==================
*/
void etClient::SetPing (int ping){

	this->ping = ping;
}

/*
 ==================
 etClient::AddEvent
 ==================
*/
void etClient::AddEvent (int eventType, int eventParm){

	int		index;

	index = state.eventSequence & (MAX_EVENTS - 1);

	state.eventSequence++;
	state.eventTypes[index] = eventType;
	state.eventParms[index] = eventParm;
}